<?php

namespace App\Master\Framework\Library\Captcha;

use Illuminate\Support\Facades\Cache;

class ApiCaptcha {
    protected $config =	array(
        'key'           =>  'WUHANG',
        'code_set'      =>  '1234567890',
        'expire'        =>  1800,
        'use_img_bg'    =>  false,
        'font_size'     =>  17,
        'use_curve'     =>  true,
        'use_noise'     =>  true,
        'image_height'  =>  35,
        'image_width'   =>  100,
        'length'        =>  4,
        'font_ttf'      =>  '',
        'bg'            =>  array(247, 247, 247),
        'reset'         =>  true,
    );
    private $_image   = NULL;
    private $_color   = NULL;

    /**
     * 架构方法 设置参数
     * @access public
     * @param  array $config 配置参数
     */
    public function __construct($config=array()){
        $this->config = array_merge($this->config, $config);
    }

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor(){
        return 'Captcha';
    }

    /**
     * 使用 $this->name 获取配置
     * @access public
     * @param  string $name 配置名称
     * @return multitype    配置值
     */
    public function __get($name) {
        return $this->config[$name];
    }

    /**
     * 设置验证码配置
     * @access public
     * @param  string $name 配置名称
     * @param  string $value 配置值
     * @return void
     */
    public function __set($name, $value){
        if(isset($this->config[$name])) {
            $this->config[$name] = $value;
        }
    }

    /**
     * 检查配置
     * @access public
     * @param  string $name 配置名称
     * @return bool
     */
    public function __isset($name){
        return isset($this->config[$name]);
    }

    /**
     * 验证验证码是否正确
     * @access public
     * @param string $code 用户验证码
     * @param string $id 验证码标识
     * @return bool 用户验证码是否正确
     */
    public function check($code, $id = '') {
        $key = ($this->authCode($this->config['key'])).$id;
        // 验证码不能为空
        $now_code = Cache::get($key);

        $params = !empty($now_code)?$now_code:'';
        if(empty($code) || empty($params)) {
            return false;
        }
        // session 缓存 过期
        if(time() - $params['time'] > $this->config['expire']) {
            //$_SESSION[$key] = null;
            Cache::forget($key);
            return false;
        }

        if($this->authCode(strtoupper($code)) == $params['code']) {
            $this->config['reset'] = null;
            Cache::forget($key);
            return true;
        }
        return false;
    }

    /**
     * 输出验证码并把验证码的值保存的session中
     * 验证码保存到session的格式为： array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间');
     * @access public
     * @param string $id 要生成验证码的标识
     * @return void
     */
    public function entry($id = '') {
        // 图片宽(px)
        $this->config['image_width'] || $this->config['image_width'] = $this->config['length']*$this->config['font_size']*1.5 + $this->config['length']*$this->config['font_size']/2;
        // 图片高(px)
        $this->config['image_height'] || $this->config['image_height'] = $this->config['font_size'] * 2.5;
        // 建立一幅 $this->config['image_width'] x $this->config['image_height'] 的图像
        $this->_image = imagecreate($this->config['image_width'], $this->config['image_height']);
        // 设置背景
        imagecolorallocate($this->_image, $this->config['bg'][0], $this->config['bg'][1], $this->config['bg'][2]);

        ImageCreateTrueColor($this->config['image_width'], $this->config['image_height']);

        // 验证码字体随机颜色
        $this->_color = imagecolorallocate($this->_image, 85, 85, 85);

        // 验证码使用随机字体
        $ttfPath = dirname(__FILE__) . '/' . ('ttfs') . '/';
        if(empty($this->config['font_ttf'])){
            $dir = dir($ttfPath);
            $ttf_arr = array();
            while (false !== ($file = $dir->read())) {
                if($file[0] != '.' && substr($file, -4) == '.ttf') {
                    $ttf_arr[] = $file;
                }
            }
            $dir->close();
            $this->config['font_ttf'] = $ttf_arr[1];
        }

        $this->config['font_ttf'] = $ttfPath . $this->config['font_ttf'];

        /*绘背景*/
        if($this->config['use_img_bg']) {
            $this->_background();
        }

        // 绘杂点
        if ($this->config['use_noise']) {
            $this->_writeNoise();
        }

        // 绘干扰线
        if ($this->config['use_curve']) {
            $this->_writeCurve();
        }

        // 绘验证码
        $code = array(); // 验证码

        for ($i = 0; $i < $this->config['length']; $i++) {
            $codeNX = [0=>mt_rand(2, 10), 1=>mt_rand(20, 33), 2=>mt_rand(45,60), 3=>mt_rand(70,78)]; // 验证码第N个字符的左边距
            $code[$i] = $this->config['code_set'][mt_rand(0, strlen($this->config['code_set'])-1)];
            imagettftext($this->_image, $this->config['font_size'], mt_rand(-10, 10), $codeNX[$i], $this->config['font_size']*1.5, $this->_color, $this->config['font_ttf'], $code[$i]);
        }

        // 保存验证码
        $key        =   $this->authCode($this->config['key']);
        $code       =   $this->authCode(strtoupper(implode('', $code)));
        $params     =   [];
        $params['code'] = $code; // 把校验码保存到session
        $params['time'] = time();  // 验证码创建时间
        Cache::put($key.$id,$params,300);
        //$_SESSION[$key.$id] = $params;

        header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate');
        header('Cache-Control: post-check=0, pre-check=0', false);
        header('Pragma: no-cache');
        header("content-type: image/png");
        // 输出图像
        imagepng($this->_image);
        imagedestroy($this->_image);
    }

    /**
     * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
     *
     *      高中的数学公式咋都忘了涅，写出来
     *		正弦型函数解析式：y=Asin(ωx+φ)+b
     *      各常数值对函数图像的影响：
     *        A：决定峰值（即纵向拉伸压缩的倍数）
     *        b：表示波形在Y轴的位置关系或纵向移动距离（上加下减）
     *        φ：决定波形与X轴位置关系或横向移动距离（左加右减）
     *        ω：决定周期（最小正周期T=2π/∣ω∣）
     *
     */
    private function _writeCurve() {
        $px = $py = 2;

        // 曲线前部分
        $A = mt_rand(1, $this->config['image_height']/2);                  // 振幅
        $b = mt_rand(-$this->config['image_height']/4, $this->config['image_height']/4);   // Y轴方向偏移量
        $f = mt_rand(-$this->config['image_height']/4, $this->config['image_height']/4);   // X轴方向偏移量
        $T = mt_rand($this->config['image_height'], $this->config['image_width']*2);  // 周期
        $w = (2* M_PI)/$T;

        $px1 = 0;  // 曲线横坐标起始位置
        $px2 = mt_rand($this->config['image_width']/2, $this->config['image_width'] * 0.8);  // 曲线横坐标结束位置

        for ($px=$px1; $px<=$px2; $px = $px + 1) {
            if ($w!=0) {
                $py = $A * sin($w*$px + $f)+ $b + $this->config['image_height']/2;  // y = Asin(ωx+φ) + b
                $i = (int) ($this->config['font_size']/20);
                while ($i > 0) {
                    imagesetpixel($this->_image, $px + $i , $py + $i, $this->_color);  // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出（不用这while循环）性能要好很多
                    $i--;
                }
            }
        }

        // 曲线后部分
        $A = mt_rand(1, $this->config['image_height']/2);                  // 振幅
        $f = mt_rand(-$this->config['image_height']/4, $this->config['image_height']/4);   // X轴方向偏移量
        $T = mt_rand($this->config['image_height'], $this->config['image_width']*2);  // 周期
        $w = (2* M_PI)/$T;
        $b = $py - $A * sin($w*$px + $f) - $this->config['image_height']/2;
        $px1 = $px2;
        $px2 = $this->config['image_width'];

        for ($px=$px1; $px<=$px2; $px=$px+ 1) {
            if ($w!=0) {
                $py = $A * sin($w*$px + $f)+ $b + $this->config['image_height']/2;  // y = Asin(ωx+φ) + b
                $i = (int) ($this->config['font_size']/5);
                while ($i > 0) {
                    imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color);
                    $i--;
                }
            }
        }
    }

    /**
     * 画杂点
     * 往图片上写不同颜色的点 $codeSet 可以设置任意字符
     */
    private function _writeNoise() {
        $codeSet = '....................................................';
        for($i = 0; $i < 10; $i++){
            //杂点颜色
            $noiseColor = imagecolorallocate($this->_image, mt_rand(150,225), mt_rand(150,225), mt_rand(150,225));
            for($j = 0; $j < 5; $j++) {
                // 绘杂点
                imagestring($this->_image, 10, mt_rand(-10, $this->config['image_width']),  mt_rand(-10, $this->config['image_height']), $codeSet[mt_rand(0, 29)], $noiseColor);
            }
        }
    }

    /**
     * 绘制背景图片
     * 注：如果验证码输出图片比较大，将占用比较多的系统资源
     */
    private function _background() {
        $path = dirname(__FILE__).'/Verify/bgs/';
        $dir = dir($path);

        $bgs = array();
        while (false !== ($file = $dir->read())) {
            if($file[0] != '.' && substr($file, -4) == '.jpg') {
                $bgs[] = $path . $file;
            }
        }
        $dir->close();

        $gb = $bgs[array_rand($bgs)];

        list($width, $height) = @getimagesize($gb);
        // Resample
        $bgImage = @imagecreatefromjpeg($gb);
        @imagecopyresampled($this->_image, $bgImage, 0, 0, 0, 0, $this->config['image_width'], $this->config['image_height'], $width, $height);
        @imagedestroy($bgImage);
    }

    /* 加密验证码 */
    private function authCode($str){
        $key = substr(md5($this->config['key']), 5, 8);
        $str = substr(md5($str), 8, 10);
        return md5($key . $str);
    }

}
